import {
  cadAppLog,
  CSPoint,
  pickedFaceData,
  pickedLineData,
  pickedVertexData,
  POINT_TYPE,
} from '@amcax/base';

import { Mark } from '@app-cad/types';
import { getNameOfPickedObj } from '../utils/render';
import { MARK_EVENT } from '../Constants';
import { CSS2DMarkObject } from './CSS2DMarkObject';
import { Vector3 } from 'three-legacy';
import {
  ctx2,
  dataManager,
  docMgr,
  userEvents,
  widgetManager,
} from '@app-cad/module';
import { httpApi, wsApi } from '@app-cad/server';

const getColorFromId = (id: number) => [
  ((id >> 16) & 255) / 255,
  ((id >> 8) & 255) / 255,
  (id & 255) / 255,
];

export type MarkRenderInfo = {
  tag: CSS2DMarkObject;
  pickTag: CSPoint;
  mark: Mark;
  active: boolean;
};

class MarkManager {
  private _marks: Mark[] = [];
  private _tagsMap: Map<string, MarkRenderInfo> = new Map();
  private _userMap: Map<string, any> = new Map();

  getMarkUnionId(mark: Mark) {
    return `${mark.targetId}-${mark.userId}-${mark.createdTime}`;
  }

  getMarkTargetId(mark: Mark) {
    return `${mark.dxid}-${mark.targetId}`;
  }

  getUserByMark(mark: Mark) {
    return this._userMap.get(mark.userId);
  }

  async updateMarks() {
    const flatDocumentObjects = docMgr.flatDocumentObjects;
    this._marks = flatDocumentObjects
      .flatMap((o) => (o.mark || []) as Mark[])
      .sort((a, b) => b.createdTime - a.createdTime);

    await this.loadUsers();

    Array.from(this._tagsMap.values()).forEach((tag) => {
      if (!this._marks.some((mark) => tag.mark.dxid === mark.dxid)) {
        this._tagsMap.delete(this.getMarkUnionId(tag.mark));
      }
    });

    userEvents.emit(MARK_EVENT.MARK_UPDATED);
    userEvents.emit(MARK_EVENT.MARK_RENDER_CHANGED, this._tagsMap);

    cadAppLog('[debug]loadMarks', this._marks);
  }

  async loadUsers() {
    const userIds = this._marks.map((mark) => {
      return mark.userId;
    });
    if (userIds.length > 0) {
      const { data } = await httpApi.listUsers(userIds);
      cadAppLog('[debug]users', data);
      this._userMap = new Map(data.map((user) => [user.uuid, user]));
    }
  }

  async loadUserById(id: string) {
    const { data } = await httpApi.listUsers([id]);
    cadAppLog('[debug]users', data);
    const user = data.length > 0 ? data[0] : null;
    if (user) {
      this._userMap.set(user.uuid, user);
    }
  }

  get marks() {
    return this._marks;
  }

  getPickedObjCenter(
    pickedObject: pickedFaceData | pickedLineData | pickedVertexData,
  ) {
    const idInfo = pickedObject.idInfo;
    const vertexId = pickedObject['vertexId'];
    const edgeId = pickedObject['edgeId'];
    const meshId = pickedObject['meshId'];

    const modelData = pickedObject.modelData;
    const targetIndex = idInfo.i;

    if (vertexId) {
      // 直接获取定点坐标
      return new Vector3(
        modelData.points[targetIndex][0],
        modelData.points[targetIndex][1],
        modelData.points[targetIndex][2],
      );
    } else if (edgeId) {
      // 计算边的中心点
      const startPoint = new Vector3(
        modelData.edges[targetIndex].mesh[0][0],
        modelData.edges[targetIndex].mesh[0][1],
        modelData.edges[targetIndex].mesh[0][2],
      );
      const endPoint = new Vector3(
        modelData.edges[targetIndex].mesh[1][0],
        modelData.edges[targetIndex].mesh[1][1],
        modelData.edges[targetIndex].mesh[1][2],
      );
      return new Vector3().addVectors(startPoint, endPoint).multiplyScalar(0.5);
    } else if (meshId) {
      // 计算面的中心点
      return new Vector3(
        modelData.faces[targetIndex].center[0],
        modelData.faces[targetIndex].center[1],
        modelData.faces[targetIndex].center[2],
      );
    }

    return null;
  }

  async addMark(
    pickedObject: pickedFaceData | pickedLineData | pickedVertexData,
    text: string,
    position?: Vector3,
  ) {
    cadAppLog('[debug]addMark', pickedObject);
    if (pickedObject) {
      const name = getNameOfPickedObj(pickedObject);
      const idInfo = pickedObject.idInfo;

      if (idInfo) {
        const dxid = idInfo.o;
        const vertexId = pickedObject['vertexId'];
        const edgeId = pickedObject['edgeId'];
        const meshId = pickedObject['meshId'];

        const targetId = vertexId ?? edgeId ?? meshId ?? dxid;
        cadAppLog('[debug]targetId', targetId);

        let targetType: string;

        if (vertexId) {
          targetType = 'vertex';
        } else if (edgeId) {
          targetType = 'edge';
        } else if (meshId) {
          targetType = 'mesh';
        }

        const center: Vector3 =
          position ?? this.getPickedObjCenter(pickedObject);

        const userId = ctx2.userUUID;
        await this.loadUserById(userId);

        const objectInfo = dataManager.getObjectInfoByDxid(dxid);
        const marks = objectInfo.mark ?? [];
        const mark: Mark = {
          userId,
          name,
          dxid,
          targetId,
          targetType,
          center: new Vector3(center.x, center.y, center.z),
          text,
          createdTime: Date.now(),
        };
        marks.push(mark);

        await wsApi.editMark(dxid, marks);

        this.showMark(mark);
        ctx2.viewer.requestRender();

        await docMgr.updateDocumentObjects();

        await this.updateMarks();

        widgetManager.rightNavigation.show('mark', pickedObject);
      }
    }
  }

  async removeMark(mark: Mark) {
    const dxid = mark.dxid;
    const objectInfo = dataManager.getObjectInfoByDxid(dxid);
    let marks = objectInfo.mark as Mark[];
    marks = marks.filter((m) => {
      return this.getMarkUnionId(m) !== this.getMarkUnionId(mark);
    });
    await wsApi.editMark(dxid, marks);
    const { tag: _removed_tag, pickTag } = this._tagsMap.get(
      this.getMarkUnionId(mark),
    );
    if (_removed_tag) {
      ctx2.cadScene.toolsGroup.remove(_removed_tag);
      ctx2.viewer.sceneSetup.pickScene.remove(pickTag);
      pickTag.destroy();
      ctx2.viewer.requestRender();
      this._tagsMap.delete(this.getMarkUnionId(mark));
      await docMgr.updateDocumentObjects();
      await this.updateMarks();
    }

    userEvents.emit(MARK_EVENT.MARK_RENDER_CHANGED, this._tagsMap);
  }

  showMark(mark: Mark) {
    const currentTag = this._tagsMap.get(this.getMarkUnionId(mark));
    if (currentTag != null) {
      currentTag.active = true;
      ctx2.cadScene.toolsGroup.add(currentTag.tag);
      ctx2.viewer.sceneSetup.pickScene.add(currentTag.pickTag);

      userEvents.emit(MARK_EVENT.MARK_RENDER_CHANGED, this._tagsMap);
      ctx2.viewer.sceneSetup.updatePixelBuffer();
      return;
    }
    const center: Vector3 = new Vector3(
      mark.center.x,
      mark.center.y,
      mark.center.z,
    );

    const tag = new CSS2DMarkObject(document.createElement('div'));
    tag.name = this.getMarkUnionId(mark);

    const pickTagColorID = ++dataManager.colorId;
    tag.colorID = pickTagColorID;

    const pickTag = new CSPoint({
      pointType: POINT_TYPE.PICK,
      size: 2,
      depthTest: true,
    });
    pickTag.setVertexs(
      [center.x, center.y, center.z],
      getColorFromId(pickTagColorID),
    );

    tag.position.set(center.x, center.y, center.z);
    this._tagsMap.set(this.getMarkUnionId(mark), {
      tag,
      mark,
      active: true,
      pickTag,
    });

    ctx2.cadScene.toolsGroup.add(tag);
    ctx2.viewer.sceneSetup.pickScene.add(pickTag);
    ctx2.viewer.sceneSetup.updatePixelBuffer();

    userEvents.emit(MARK_EVENT.MARK_RENDER_CHANGED, this._tagsMap);
  }

  async showAllMarks() {
    await this.updateMarks();
    await docMgr.updateDocumentObjects();
    if (this._marks && this._marks.length > 0) {
      for (const mark of this._marks) {
        this.showMark(mark);
      }
      ctx2.viewer.requestRender();
      ctx2.viewer.sceneSetup.updatePixelBuffer();
    }
  }

  hideAllMarks() {
    if (this._tagsMap.size > 0) {
      for (const tagItem of this._tagsMap.values()) {
        tagItem.active = false;
        ctx2.cadScene.toolsGroup.remove(tagItem.tag);
        ctx2.viewer.sceneSetup.pickScene.add(tagItem.pickTag);
      }
      ctx2.viewer.requestRender();
      ctx2.viewer.sceneSetup.updatePixelBuffer();
    }

    userEvents.emit(MARK_EVENT.MARK_RENDER_CHANGED, this._tagsMap);
  }

  pickAndAddMark(pickedObject) {
    widgetManager.rightNavigation.show('mark', pickedObject);
    userEvents.emit(MARK_EVENT.MARK_PICKED_OBJECT, pickedObject);
  }
}

export const markManager = new MarkManager();
